import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.feature_extraction.text import TfidfVectorizer
from django.contrib.auth.models import User
from .models import Movie, Rating, Genre
from django.db.models import Avg


class MovieRecommendationService:
    """电影推荐服务"""
    
    def __init__(self):
        self.user_item_matrix = None
        self.movie_features = None
        self.user_similarity = None
        self.item_similarity = None
    
    def build_user_item_matrix(self):
        """构建用户-电影评分矩阵"""
        ratings = Rating.objects.select_related('user', 'movie').all()
        
        # 创建评分数据框
        data = []
        for rating in ratings:
            data.append({
                'user_id': rating.user.id,
                'movie_id': rating.movie.id,
                'rating': rating.rating
            })
        
        df = pd.DataFrame(data)
        
        if df.empty:
            return pd.DataFrame()
        
        # 创建用户-电影矩阵
        self.user_item_matrix = df.pivot_table(
            index='user_id',
            columns='movie_id',
            values='rating',
            fill_value=0
        )
        
        return self.user_item_matrix
    
    def calculate_user_similarity(self):
        """计算用户相似度"""
        if self.user_item_matrix is None or self.user_item_matrix.empty:
            self.build_user_item_matrix()
        
        if self.user_item_matrix.empty:
            return np.array([])
        
        # 计算余弦相似度
        self.user_similarity = cosine_similarity(self.user_item_matrix)
        return self.user_similarity
    
    def calculate_item_similarity(self):
        """计算电影相似度"""
        if self.user_item_matrix is None or self.user_item_matrix.empty:
            self.build_user_item_matrix()
        
        if self.user_item_matrix.empty:
            return np.array([])
        
        # 转置矩阵以计算电影间相似度
        item_matrix = self.user_item_matrix.T
        self.item_similarity = cosine_similarity(item_matrix)
        return self.item_similarity
    
    def get_user_based_recommendations(self, user_id, num_recommendations=10):
        """基于用户的协同过滤推荐"""
        if self.user_similarity is None:
            self.calculate_user_similarity()
        
        if self.user_item_matrix is None or self.user_item_matrix.empty:
            return self.get_popular_movies(num_recommendations)
        
        try:
            user_idx = list(self.user_item_matrix.index).index(user_id)
        except ValueError:
            # 用户不在矩阵中，返回热门电影
            return self.get_popular_movies(num_recommendations)
        
        # 获取用户相似度
        user_similarities = self.user_similarity[user_idx]
        
        # 获取用户已评分的电影
        user_ratings = self.user_item_matrix.iloc[user_idx]
        rated_movies = user_ratings[user_ratings > 0].index.tolist()
        
        # 计算推荐分数
        recommendations = []
        
        for movie_id in self.user_item_matrix.columns:
            if movie_id in rated_movies:
                continue  # 跳过已评分的电影
            
            # 计算预测评分
            numerator = 0
            denominator = 0
            
            for other_user_idx, similarity in enumerate(user_similarities):
                if other_user_idx == user_idx or similarity <= 0:
                    continue
                
                other_user_rating = self.user_item_matrix.iloc[other_user_idx, 
                    list(self.user_item_matrix.columns).index(movie_id)]
                
                if other_user_rating > 0:
                    numerator += similarity * other_user_rating
                    denominator += abs(similarity)
            
            if denominator > 0:
                predicted_rating = numerator / denominator
                
                try:
                    movie = Movie.objects.get(id=movie_id)
                    recommendations.append({
                        'movie': movie,
                        'predicted_rating': predicted_rating,
                        'similarity_score': denominator
                    })
                except Movie.DoesNotExist:
                    continue
        
        # 按预测评分排序
        recommendations.sort(key=lambda x: x['predicted_rating'], reverse=True)
        return recommendations[:num_recommendations]
    
    def get_item_based_recommendations(self, user_id, num_recommendations=10):
        """基于物品的协同过滤推荐"""
        if self.item_similarity is None:
            self.calculate_item_similarity()
        
        if self.user_item_matrix is None or self.user_item_matrix.empty:
            return self.get_popular_movies(num_recommendations)
        
        try:
            user_ratings = self.user_item_matrix.loc[user_id]
        except KeyError:
            return self.get_popular_movies(num_recommendations)
        
        # 获取用户已评分的电影
        rated_movies = user_ratings[user_ratings > 0]
        
        if rated_movies.empty:
            return self.get_popular_movies(num_recommendations)
        
        recommendations = []
        
        for movie_id in self.user_item_matrix.columns:
            if movie_id in rated_movies.index:
                continue  # 跳过已评分的电影
            
            try:
                movie_idx = list(self.user_item_matrix.columns).index(movie_id)
            except ValueError:
                continue
            
            # 计算与已评分电影的相似度加权评分
            numerator = 0
            denominator = 0
            
            for rated_movie_id, rating in rated_movies.items():
                try:
                    rated_movie_idx = list(self.user_item_matrix.columns).index(rated_movie_id)
                    similarity = self.item_similarity[movie_idx][rated_movie_idx]
                    
                    if similarity > 0:
                        numerator += similarity * rating
                        denominator += abs(similarity)
                except (ValueError, IndexError):
                    continue
            
            if denominator > 0:
                predicted_rating = numerator / denominator
                
                try:
                    movie = Movie.objects.get(id=movie_id)
                    recommendations.append({
                        'movie': movie,
                        'predicted_rating': predicted_rating,
                        'similarity_score': denominator
                    })
                except Movie.DoesNotExist:
                    continue
        
        # 按预测评分排序
        recommendations.sort(key=lambda x: x['predicted_rating'], reverse=True)
        return recommendations[:num_recommendations]
    
    def get_content_based_recommendations(self, user_id, num_recommendations=10):
        """基于内容的推荐"""
        try:
            user = User.objects.get(id=user_id)
            user_ratings = Rating.objects.filter(user=user, rating__gte=4.0)
            
            if not user_ratings.exists():
                return self.get_popular_movies(num_recommendations)
            
            # 获取用户喜欢的电影类型
            liked_genres = set()
            for rating in user_ratings:
                for genre in rating.movie.genres.all():
                    liked_genres.add(genre.name)
            
            if not liked_genres:
                return self.get_popular_movies(num_recommendations)
            
            # 获取用户未评分的电影
            rated_movie_ids = user_ratings.values_list('movie_id', flat=True)
            unrated_movies = Movie.objects.exclude(id__in=rated_movie_ids)
            
            recommendations = []
            
            for movie in unrated_movies:
                movie_genres = set(genre.name for genre in movie.genres.all())
                
                # 计算类型匹配度
                genre_overlap = len(liked_genres.intersection(movie_genres))
                genre_score = genre_overlap / len(liked_genres) if liked_genres else 0
                
                if genre_score > 0:
                    # 获取电影平均评分
                    avg_rating = movie.rating_set.aggregate(Avg('rating'))['rating__avg'] or 0
                    
                    recommendations.append({
                        'movie': movie,
                        'predicted_rating': avg_rating,
                        'similarity_score': genre_score
                    })
            
            # 按相似度和平均评分排序
            recommendations.sort(
                key=lambda x: (x['similarity_score'], x['predicted_rating']),
                reverse=True
            )
            
            return recommendations[:num_recommendations]
            
        except User.DoesNotExist:
            return self.get_popular_movies(num_recommendations)
    
    def get_popular_movies(self, num_recommendations=10):
        """获取热门电影（备用推荐）"""
        popular_movies = Movie.objects.annotate(
            avg_rating=Avg('rating__rating')
        ).filter(
            avg_rating__isnull=False
        ).order_by('-avg_rating')[:num_recommendations]
        
        recommendations = []
        for movie in popular_movies:
            recommendations.append({
                'movie': movie,
                'predicted_rating': movie.avg_rating,
                'similarity_score': 1.0
            })
        
        return recommendations
    
    def get_hybrid_recommendations(self, user_id, num_recommendations=10):
        """混合推荐算法"""
        # 获取不同算法的推荐结果
        user_based = self.get_user_based_recommendations(user_id, num_recommendations)
        item_based = self.get_item_based_recommendations(user_id, num_recommendations)
        content_based = self.get_content_based_recommendations(user_id, num_recommendations)
        
        # 合并推荐结果
        all_recommendations = {}
        
        # 用户协同过滤权重：0.4
        for rec in user_based:
            movie_id = rec['movie'].id
            if movie_id not in all_recommendations:
                all_recommendations[movie_id] = {
                    'movie': rec['movie'],
                    'score': 0,
                    'count': 0
                }
            all_recommendations[movie_id]['score'] += rec['predicted_rating'] * 0.4
            all_recommendations[movie_id]['count'] += 1
        
        # 物品协同过滤权重：0.4
        for rec in item_based:
            movie_id = rec['movie'].id
            if movie_id not in all_recommendations:
                all_recommendations[movie_id] = {
                    'movie': rec['movie'],
                    'score': 0,
                    'count': 0
                }
            all_recommendations[movie_id]['score'] += rec['predicted_rating'] * 0.4
            all_recommendations[movie_id]['count'] += 1
        
        # 基于内容推荐权重：0.2
        for rec in content_based:
            movie_id = rec['movie'].id
            if movie_id not in all_recommendations:
                all_recommendations[movie_id] = {
                    'movie': rec['movie'],
                    'score': 0,
                    'count': 0
                }
            all_recommendations[movie_id]['score'] += rec['predicted_rating'] * 0.2
            all_recommendations[movie_id]['count'] += 1
        
        # 计算最终推荐
        final_recommendations = []
        for movie_id, data in all_recommendations.items():
            final_score = data['score'] / data['count'] if data['count'] > 0 else 0
            final_recommendations.append({
                'movie': data['movie'],
                'predicted_rating': final_score,
                'similarity_score': data['count'] / 3.0  # 归一化算法数量
            })
        
        # 按分数排序
        final_recommendations.sort(key=lambda x: x['predicted_rating'], reverse=True)
        
        return final_recommendations[:num_recommendations]